#!/bin/bash
# {{ ansible_managed }}
# ============================================================================
# 数据库备份脚本 - Database Backup Script
# ============================================================================
# 功能说明:
#   - 支持MySQL和PostgreSQL数据库的完整备份
#   - 提供多种备份格式（SQL转储、自定义格式）
#   - 自动压缩和校验和生成
#   - 备份配置文件和用户权限
#   - 完整性验证和清单生成
# 
# 使用方法:
#   ./backup_database.sh [backup_type]
#   backup_type: daily|weekly|monthly (默认: daily)
# 
# 依赖工具:
#   - mysqldump (MySQL备份)
#   - pg_dump, pg_dumpall (PostgreSQL备份)
#   - gzip (压缩)
#   - sha256sum (校验和)
# ============================================================================

# 启用严格模式，确保脚本安全性
# -e: 遇到错误立即退出
# -u: 使用未定义变量时报错
# -o pipefail: 管道中任何命令失败都会导致整个管道失败
set -euo pipefail

# ============================================================================
# 配置变量 - Configuration Variables
# ============================================================================

# 备份类型，从命令行参数获取，默认为daily
BACKUP_TYPE="${1:-daily}"

# 备份根目录，由Ansible模板变量提供
BACKUP_BASE_DIR="{{ backup_base_dir }}"

# 日志目录，存储备份过程的详细日志
BACKUP_LOG_DIR="{{ backup_log_dir }}"

# 时间戳，用于创建唯一的备份文件名
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# 日志文件路径，记录数据库备份的详细过程
LOG_FILE="${BACKUP_LOG_DIR}/database_${BACKUP_TYPE}_${TIMESTAMP}.log"

# 数据库备份目录，按类型和时间戳组织
DB_BACKUP_DIR="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/databases/${TIMESTAMP}"

# ============================================================================
# 工具函数 - Utility Functions
# ============================================================================

# 日志记录函数 - 记录带时间戳的日志信息
# 参数: $1 - 日志消息
# 功能: 同时输出到控制台和日志文件
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 错误退出函数 - 记录错误信息并退出脚本
# 参数: $1 - 错误消息
# 功能: 记录错误日志并以非零状态码退出
error_exit() {
    log "ERROR: $1"
    exit 1
}

# MySQL连接测试函数 - 验证MySQL数据库连接可用性
# 参数: $1 - 主机地址
#       $2 - 端口号
#       $3 - 用户名
#       $4 - 密码
# 返回: 0表示连接成功，非0表示连接失败
check_mysql_connection() {
    local host="$1"
    local port="$2"
    local username="$3"
    local password="$4"
    
    # 执行简单的SELECT查询测试连接
    # 重定向输出到/dev/null以避免显示查询结果
    mysql -h "$host" -P "$port" -u "$username" -p"$password" -e "SELECT 1;" >/dev/null 2>&1
}

# PostgreSQL连接测试函数 - 验证PostgreSQL数据库连接可用性
# 参数: $1 - 主机地址
#       $2 - 端口号
#       $3 - 用户名
#       $4 - 密码
#       $5 - 数据库名
# 返回: 0表示连接成功，非0表示连接失败
check_postgresql_connection() {
    local host="$1"
    local port="$2"
    local username="$3"
    local password="$4"
    local database="$5"
    
    # 设置PostgreSQL密码环境变量
    export PGPASSWORD="$password"
    # 执行简单的SELECT查询测试连接
    psql -h "$host" -p "$port" -U "$username" -d "$database" -c "SELECT 1;" >/dev/null 2>&1
    # 清理密码环境变量，确保安全性
    unset PGPASSWORD
}

# ============================================================================
# MySQL数据库备份函数 - MySQL Database Backup Function
# ============================================================================

# MySQL数据库备份函数 - 执行MySQL数据库的完整备份
# 参数: $1 - 数据库实例名称
#       $2 - 主机地址
#       $3 - 端口号
#       $4 - 用户名
#       $5 - 密码
#       $6+ - 要备份的数据库列表
# 功能: 创建SQL转储、压缩备份、生成校验和、备份配置和权限
backup_mysql_database() {
    local db_name="$1"
    local host="$2"
    local port="$3"
    local username="$4"
    local password="$5"
    shift 5
    local databases=("$@")
    
    log "Starting MySQL backup for $db_name"
    
    # 测试数据库连接可用性
    if ! check_mysql_connection "$host" "$port" "$username" "$password"; then
        error_exit "Cannot connect to MySQL server $host:$port"
    fi
    
    # 创建MySQL备份目录
    local mysql_backup_dir="${DB_BACKUP_DIR}/mysql/${db_name}"
    mkdir -p "$mysql_backup_dir"
    
    # 逐个备份指定的数据库
    for database in "${databases[@]}"; do
        log "Backing up MySQL database: $database"
        
        local backup_file="${mysql_backup_dir}/${database}_${TIMESTAMP}.sql"
        
        # 使用mysqldump创建数据库转储
        # 各选项说明:
        # --single-transaction: 确保InnoDB表的一致性备份
        # --routines: 备份存储过程和函数
        # --triggers: 备份触发器
        # --events: 备份事件调度器
        # --hex-blob: 使用十六进制格式备份BLOB数据
        # --opt: 启用优化选项（等同于多个选项的组合）
        # --lock-tables=false: 不锁定表（与single-transaction配合使用）
        # --flush-logs: 备份前刷新日志
        # --master-data=2: 记录二进制日志位置（注释形式）
        mysqldump \
            -h "$host" \
            -P "$port" \
            -u "$username" \
            -p"$password" \
            --single-transaction \
            --routines \
            --triggers \
            --events \
            --hex-blob \
            --opt \
            --lock-tables=false \
            --flush-logs \
            --master-data=2 \
            "$database" > "$backup_file"
        
        # 检查备份是否成功
        if [[ $? -eq 0 ]]; then
            log "MySQL database $database backed up successfully to $backup_file"
            
            # 压缩备份文件以节省存储空间
            gzip "$backup_file"
            log "Backup compressed: ${backup_file}.gz"
            
            # 生成SHA256校验和用于完整性验证
            sha256sum "${backup_file}.gz" > "${backup_file}.gz.sha256"
            
        else
            error_exit "Failed to backup MySQL database $database"
        fi
    done
    
    # 备份MySQL配置文件
    # 保存MySQL服务器配置以便完整恢复
    if [[ -f "/etc/my.cnf" ]]; then
        cp "/etc/my.cnf" "${mysql_backup_dir}/my.cnf_${TIMESTAMP}"
        log "MySQL configuration backed up"
    fi
    
    # 备份MySQL用户和权限信息
    # 生成所有用户的GRANT语句，用于恢复用户权限
    mysql -h "$host" -P "$port" -u "$username" -p"$password" \
        -e "SELECT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') AS query FROM mysql.user" \
        --skip-column-names | mysql -h "$host" -P "$port" -u "$username" -p"$password" \
        --skip-column-names > "${mysql_backup_dir}/grants_${TIMESTAMP}.sql"
    
    log "MySQL backup for $db_name completed"
}

# ============================================================================
# PostgreSQL数据库备份函数 - PostgreSQL Database Backup Function
# ============================================================================

# PostgreSQL数据库备份函数 - 执行PostgreSQL数据库的完整备份
# 参数: $1 - 数据库实例名称
#       $2 - 主机地址
#       $3 - 端口号
#       $4 - 用户名
#       $5 - 密码
#       $6+ - 要备份的数据库列表
# 功能: 创建自定义格式和SQL格式备份、压缩备份、生成校验和、备份配置和角色
backup_postgresql_database() {
    local db_name="$1"
    local host="$2"
    local port="$3"
    local username="$4"
    local password="$5"
    shift 5
    local databases=("$@")
    
    log "Starting PostgreSQL backup for $db_name"
    
    # 设置PostgreSQL密码环境变量
    export PGPASSWORD="$password"
    
    # 创建PostgreSQL备份目录
    local pg_backup_dir="${DB_BACKUP_DIR}/postgresql/${db_name}"
    mkdir -p "$pg_backup_dir"
    
    # 逐个备份指定的数据库
    for database in "${databases[@]}"; do
        log "Backing up PostgreSQL database: $database"
        
        # 测试数据库连接可用性
        if ! check_postgresql_connection "$host" "$port" "$username" "$password" "$database"; then
            log "WARNING: Cannot connect to PostgreSQL database $database, skipping"
            continue
        fi
        
        local backup_file="${pg_backup_dir}/${database}_${TIMESTAMP}.sql"
        
        # 创建自定义格式的数据库转储
        # 自定义格式支持并行恢复和选择性恢复
        # 各选项说明:
        # --no-password: 使用环境变量中的密码
        # --clean: 在重建前清理（删除）数据库对象
        # --create: 包含创建数据库的命令
        # --verbose: 详细输出备份过程
        # --format=custom: 使用PostgreSQL自定义格式
        pg_dump \
            -h "$host" \
            -p "$port" \
            -U "$username" \
            -d "$database" \
            --no-password \
            --clean \
            --create \
            --verbose \
            --format=custom \
            --file="${backup_file}.dump"
        
        # 检查备份是否成功
        if [[ $? -eq 0 ]]; then
            log "PostgreSQL database $database backed up successfully to ${backup_file}.dump"
            
            # 同时创建纯SQL格式的转储，便于查看和手动恢复
            # --format=plain: 创建纯SQL文本格式
            pg_dump \
                -h "$host" \
                -p "$port" \
                -U "$username" \
                -d "$database" \
                --no-password \
                --clean \
                --create \
                --verbose \
                --format=plain > "$backup_file"
            
            # 压缩两种格式的备份文件
            gzip "$backup_file"
            gzip "${backup_file}.dump"
            
            log "Backups compressed: ${backup_file}.gz and ${backup_file}.dump.gz"
            
            # 为两种格式生成校验和
            sha256sum "${backup_file}.gz" > "${backup_file}.gz.sha256"
            sha256sum "${backup_file}.dump.gz" > "${backup_file}.dump.gz.sha256"
            
        else
            error_exit "Failed to backup PostgreSQL database $database"
        fi
    done
    
    # 备份PostgreSQL配置文件
    # 获取配置文件目录并打包所有配置文件
    local pg_config_dir=$(psql -h "$host" -p "$port" -U "$username" -d postgres --no-password -t -c "SHOW config_file;" | xargs dirname)
    if [[ -d "$pg_config_dir" ]]; then
        tar -czf "${pg_backup_dir}/postgresql_config_${TIMESTAMP}.tar.gz" -C "$pg_config_dir" .
        log "PostgreSQL configuration backed up"
    fi
    
    # 备份PostgreSQL用户和角色信息
    # 使用pg_dumpall备份所有角色、用户和权限
    # --roles-only: 只备份角色和用户，不备份数据
    pg_dumpall \
        -h "$host" \
        -p "$port" \
        -U "$username" \
        --no-password \
        --roles-only > "${pg_backup_dir}/roles_${TIMESTAMP}.sql"
    
    # 压缩角色备份文件
    gzip "${pg_backup_dir}/roles_${TIMESTAMP}.sql"
    
    # 清理密码环境变量，确保安全性
    unset PGPASSWORD
    
    log "PostgreSQL backup for $db_name completed"
}

# ============================================================================
# 数据库备份验证函数 - Database Backup Verification Function
# ============================================================================

# 数据库备份验证函数 - 验证数据库备份文件的完整性和校验和
# 功能: 检查MySQL和PostgreSQL备份文件的压缩完整性和SHA256校验和
# 返回: 验证失败时调用error_exit退出脚本
verify_database_backup() {
    log "Verifying database backups..."
    
    local verification_failed=false
    
    # 验证MySQL备份文件
    # 查找所有MySQL SQL备份文件并验证其完整性
    if [[ -d "${DB_BACKUP_DIR}/mysql" ]]; then
        find "${DB_BACKUP_DIR}/mysql" -name "*.sql.gz" | while read -r backup_file; do
            # 使用gzip -t测试压缩文件的完整性
            if ! gzip -t "$backup_file"; then
                log "ERROR: MySQL backup file $backup_file is corrupted"
                verification_failed=true
            fi
            
            # 验证SHA256校验和（如果存在）
            if [[ -f "${backup_file}.sha256" ]]; then
                if ! sha256sum -c "${backup_file}.sha256"; then
                    log "ERROR: MySQL backup file $backup_file checksum verification failed"
                    verification_failed=true
                fi
            fi
        done
    fi
    
    # 验证PostgreSQL备份文件
    # 查找所有PostgreSQL SQL和自定义格式备份文件并验证其完整性
    if [[ -d "${DB_BACKUP_DIR}/postgresql" ]]; then
        find "${DB_BACKUP_DIR}/postgresql" -name "*.sql.gz" -o -name "*.dump.gz" | while read -r backup_file; do
            # 使用gzip -t测试压缩文件的完整性
            if ! gzip -t "$backup_file"; then
                log "ERROR: PostgreSQL backup file $backup_file is corrupted"
                verification_failed=true
            fi
            
            # 验证SHA256校验和（如果存在）
            if [[ -f "${backup_file}.sha256" ]]; then
                if ! sha256sum -c "${backup_file}.sha256"; then
                    log "ERROR: PostgreSQL backup file $backup_file checksum verification failed"
                    verification_failed=true
                fi
            fi
        done
    fi
    
    # 检查验证结果并决定是否退出
    if [[ "$verification_failed" == "true" ]]; then
        error_exit "Database backup verification failed"
    else
        log "Database backup verification successful"
    fi
}

# ============================================================================
# 数据库备份清单生成函数 - Database Backup Manifest Generation Function
# ============================================================================

# 数据库备份清单生成函数 - 生成包含备份详细信息的清单文件
# 功能: 创建包含时间戳、文件列表、大小和校验和的详细清单
# 输出: 在备份目录中创建DATABASE_MANIFEST.txt文件
generate_database_manifest() {
    log "Generating database backup manifest..."
    
    local manifest_file="${DB_BACKUP_DIR}/DATABASE_MANIFEST.txt"
    
    # 生成详细的备份清单文件
    # 包含系统信息、备份类型、文件列表和校验和
    {
        echo "Database Backup Manifest"
        echo "========================"
        echo "Timestamp: $TIMESTAMP"
        echo "Type: $BACKUP_TYPE"
        echo "Hostname: $(hostname)"
        echo "Backup Directory: $DB_BACKUP_DIR"
        echo ""
        echo "MySQL Backups:"
        # 列出MySQL备份目录中的所有文件及其详细信息
        if [[ -d "${DB_BACKUP_DIR}/mysql" ]]; then
            find "${DB_BACKUP_DIR}/mysql" -type f -exec ls -lh {} \;
        else
            echo "No MySQL backups found"
        fi
        echo ""
        echo "PostgreSQL Backups:"
        # 列出PostgreSQL备份目录中的所有文件及其详细信息
        if [[ -d "${DB_BACKUP_DIR}/postgresql" ]]; then
            find "${DB_BACKUP_DIR}/postgresql" -type f -exec ls -lh {} \;
        else
            echo "No PostgreSQL backups found"
        fi
        echo ""
        echo "Checksums:"
        # 包含所有SHA256校验和文件的内容
        find "$DB_BACKUP_DIR" -name "*.sha256" -exec cat {} \;
    } > "$manifest_file"
    
    log "Database manifest generated: $manifest_file"
}

# ============================================================================
# 主执行函数 - Main Execution Function
# ============================================================================

# 主执行函数 - 协调整个数据库备份流程
# 参数: $@ - 传递给脚本的所有命令行参数
# 功能: 创建备份目录、执行数据库备份、生成清单、验证备份
main() {
    log "Starting database backup process ($BACKUP_TYPE)..."
    
    # 创建备份目录结构
    # 确保所有必要的目录都存在
    mkdir -p "$DB_BACKUP_DIR"
    
{% if backup_database_enabled %}
{% for db in backup_databases %}
{% if db.enabled %}
    # 备份数据库实例: {{ db.name }} (类型: {{ db.type }})
    # 根据数据库类型调用相应的备份函数
    case "{{ db.type }}" in
        "mysql")
            # 执行MySQL数据库备份
            backup_mysql_database "{{ db.name }}" "{{ db.host }}" "{{ db.port }}" "{{ db.username }}" "{{ db.password }}" {{ db.databases | join(' ') }}
            ;;
        "postgresql")
            # 执行PostgreSQL数据库备份
            backup_postgresql_database "{{ db.name }}" "{{ db.host }}" "{{ db.port }}" "{{ db.username }}" "{{ db.password }}" {{ db.databases | join(' ') }}
            ;;
        *)
            # 处理未知的数据库类型
            log "WARNING: Unknown database type {{ db.type }} for {{ db.name }}"
            ;;
    esac
{% endif %}
{% endfor %}
{% else %}
    # 数据库备份功能被禁用时的处理
    log "Database backup is disabled"
{% endif %}
    
    # 备份后处理操作
    # 按顺序执行清单生成和备份验证
    generate_database_manifest  # 生成备份清单文件
    verify_database_backup      # 验证备份文件完整性
    
    log "Database backup process completed successfully"
}

# ============================================================================
# 脚本入口点 - Script Entry Point
# ============================================================================

# 执行主函数并传递所有命令行参数
# "$@" 确保参数中的空格和特殊字符被正确处理
main "$@"